package com.boot2.core.dao.mybatis;

import com.boot2.core.dao.exception.DAOException;
import com.boot2.core.dao.exception.UnsupportDataTypeException;
import com.boot2.core.utils.PrivateMethodUtil;
import com.boot2.core.utils.ReflectUtil;
import com.boot2.core.utils.StringUtils;
import org.apache.commons.beanutils.MethodUtils;

import javax.persistence.Transient;
import java.beans.IntrospectionException;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * @author zhangweilin
 * @date 2018/7/4 17:06
 * @description: mybatis层通用dao工具，只做为底层由框架调用，不由业务开发调用
 */
public class MyBatisDaoUtil {
    /**
     * 数据库方言
     */
    private static final String dialect = "mysql";
    // 反射属性列表缓存
    private static final Map<String, Field[]> fieldArrCacheMap = new HashMap<>();
    private static Map<String, String> javaJdbcTypeMap = new HashMap<>();

    static {
        /**
         * key为java类型, value为mysql类型
         */
        javaJdbcTypeMap.put("Integer", "INTEGER");
        javaJdbcTypeMap.put("String", "VARCHAR");
        javaJdbcTypeMap.put("BigDecimal", "DECIMAL");
        // javaJdbcTypeMap.put("BOOLEAN", "BOOLEAN");
        javaJdbcTypeMap.put("Boolean", "BOOLEAN");
        javaJdbcTypeMap.put("Byte", "TINYINT");
        javaJdbcTypeMap.put("Short", "SMALLINT");
        javaJdbcTypeMap.put("Long", "BIGINT");
        javaJdbcTypeMap.put("Float", "REAL");
        javaJdbcTypeMap.put("Double", "FLOAT");
        javaJdbcTypeMap.put("Date", "TIMESTAMP");

        // 枚举
        javaJdbcTypeMap.put("Enum", "VARCHAR");
    }

    @SuppressWarnings("unused")
    private static void putFieldCache(final String key, final Field[] fieldArr) {
        fieldArrCacheMap.put(key, fieldArr);
    }

    /**
     * 给指定的bean生成 mybatis sql语句中必须的串
     *
     * @param obj
     * @throws IntrospectionException
     * @throws IllegalAccessException
     * @throws IllegalArgumentException
     * @throws InvocationTargetException
     * @throws NoSuchMethodException
     * @throws UnsupportDataTypeException
     */
    @SuppressWarnings("unchecked")
    public static void setMyBatisType(Object obj) throws IntrospectionException, IllegalAccessException, IllegalArgumentException, InvocationTargetException,
            NoSuchMethodException, UnsupportDataTypeException {
        List<MyBatisType> hasValueMybatisTypeList = new ArrayList<>();// 不包括非持久字段
        List<MyBatisType> hasValueMybatisTypePkList = new ArrayList<>();// 不包括非持久字段,主键区别一下对待,兼容破sqlserver
        List<MyBatisType> hasValidValueMybatisTypeList = new ArrayList<>();// 不包括非持久字段
        List<MyBatisType> hasValidValueMybatisTypePkList = new ArrayList<>();// 不包括非持久字段,主键区别一下对待,兼容破sqlserver
        List<MyBatisType> allMybatisTypeList = new ArrayList<>(); // 所有数据类型
        List<MyBatisType> allMybatisTypePkList = new ArrayList<>(); // 所有数据类型,主键区别一下对待,兼容破sqlserver
        Map<String, String> signMap = (Map<String, String>) MethodUtils.invokeMethod(obj, "getSignMap", null);
        // System.out.println("signMap2:"+signMap);
        // long time1 = System.currentTimeMillis();
        Field[] fieldArr = getAllFieldList(obj);
        // long time2 = System.currentTimeMillis();
        // System.out.println("遍历属性消耗: " + (time2 - time1) + "ms");
        FullMatchedDbColumn fullMatchedDbColumn = obj.getClass().getAnnotation(FullMatchedDbColumn.class);
        for (Field field : fieldArr) {
            Transient transient0 = field.getAnnotation(Transient.class);
            if (null != transient0) {
                continue;
            }

            //如果是非持久化属性，忽略
            int modifiers = field.getModifiers();
            if (Modifier.isTransient(modifiers)) {
                continue;
            }
            //如果是静态属性，忽略
            modifiers = field.getModifiers();
            if (Modifier.isStatic(modifiers)) {
                continue;
            }

            //兼容外部字节码框架的侵入导致无法获取getter方法
            String fieldName = field.getName();
            if (fieldName.contains("$")) {
                continue;
            }

//            PropertyDescriptor pd = new PropertyDescriptor(field.getName(), obj.getClass());
////            Method getMethod = pd.getReadMethod();// 获得get方法
            Method getMethod = ReflectUtil.getGetMethod(obj.getClass(), fieldName);// 获得get方法
            Object o = getMethod.invoke(obj);// 执行get方法返回一个Object
            String classType = field.getType().getSimpleName();
            if (field.getType().isEnum()) {
                classType = "Enum";
            }

            checkIsPrimitive(obj, field); // 禁止使用基础类型,只检测当前类，不检测其父类

            String jdbcType = javaJdbcTypeMap.get(classType);
            if (null != jdbcType) {
                Pk pk = field.getAnnotation(Pk.class);

                ColumnAlias columnAlias = field.getAnnotation(ColumnAlias.class);
                String propertyName = null;
                if (null != columnAlias) {
                    propertyName = columnAlias.name();
                    if (StringUtils.isEmptyString(propertyName)) {
                        throw new DAOException(fieldName + "的别名不能为空串");
                    }
                } else {
                    propertyName = fieldName;
                }

                MyBatisType myBatisType = new MyBatisType();
                myBatisType.setCloumn(getColumnName(propertyName, fullMatchedDbColumn));
                myBatisType.setProperty(fieldName);
                myBatisType.setJdbcType(jdbcType);
                if (null != signMap) {
                    String sign = signMap.get(getColumnName(fieldName, fullMatchedDbColumn));
                    myBatisType.setSign(StringUtils.isNotEmpty(sign) ? sign : "=");
                }

//                com.zwlsoft.service.dao.NotCloumn notCloumn = field.getAnnotation(com.zwlsoft.service.dao.NotCloumn.class);

                if (null != o) {
                    hasValueMybatisTypeList.add(myBatisType);
                    // 如果是主键
//                    if (null != pk && pk.value()) {
                    if (null != pk) {
                        if (!"mssql".equals(dialect)) {
                            hasValueMybatisTypePkList.add(myBatisType);
                        }
                    } else {
                        hasValueMybatisTypePkList.add(myBatisType);
                    }

                    // 排除有空串String的情况
                    if (o instanceof String) {
                        String strObj = (String) o;
                        if (StringUtils.isNotEmpty(strObj)) {
                            hasValidValueMybatisTypeList.add(myBatisType);
                            // 如果是主键
//                            if (null != pk && pk.value()) {
                            if (null != pk) {
                                if (!"mssql".equals(dialect)) {
                                    hasValidValueMybatisTypePkList.add(myBatisType);
                                }
                            } else {
                                hasValidValueMybatisTypePkList.add(myBatisType);
                            }
                        }
                    }
                }

                // System.out.println("myBatisType: "+myBatisType+" , pk:
                // "+pk);
                allMybatisTypeList.add(myBatisType);

                // 如果是主键
//                if (null != pk && pk.value()) {
                if (null != pk ) {
                    if (!"mssql".equals(dialect)) {
                        allMybatisTypePkList.add(myBatisType);
                    }
                } else {
                    allMybatisTypePkList.add(myBatisType);
                }
            }
        }
        PrivateMethodUtil.invokeMethod(obj, "setHasValueMybatisTypeList", hasValueMybatisTypeList);
        PrivateMethodUtil.invokeMethod(obj, "setHasValueMybatisTypePkList", hasValueMybatisTypePkList);
        PrivateMethodUtil.invokeMethod(obj, "setHasValidValueMybatisTypeList", hasValidValueMybatisTypeList);
        PrivateMethodUtil.invokeMethod(obj, "setHasValidValueMybatisTypePkList", hasValidValueMybatisTypePkList);
        // 注入所有字段与属性的映射对
        PrivateMethodUtil.invokeMethod(obj, "setAllMybatisTypeList", allMybatisTypeList);
        PrivateMethodUtil.invokeMethod(obj, "setAllMybatisTypePkList", allMybatisTypePkList);
    }

    private static Field[] getAllFieldList(Object obj) {
        return fieldArrCacheMap.get(obj.getClass().getName());
    }

    public static String getColumnName(String str, FullMatchedDbColumn fullMatchedDbColumn) {
        boolean isFull = fullMatchedDbColumn == null ? false : fullMatchedDbColumn.value();

        // "countryNameNameNameNameNameNameName "
        if (StringUtils.isNotEmpty(str)) {
            if (isFull) {
                return str;
            }
            return getStandardColumnName(str);
        }
        return "";
    }

    public static String getStandardColumnName(String str) {
        return str.replaceAll("([a-z]+)?([A-Z]+)", "$1_$2").toLowerCase();
    }

    /**
     * 检测是否有基础类型，注意，它只检测当前类，并不检测父类，父类的属性类型请暂时自行检查
     *
     * @param obj
     * @param field
     * @throws UnsupportDataTypeException
     */
    private static void checkIsPrimitive(Object obj, Field field) throws UnsupportDataTypeException {
        // System.out.println("name: "+field.getName());
        // System.out.println("type: "+field.getType());
        if (field.getType().isPrimitive()) {
            throw new UnsupportDataTypeException("不支持基础类型：" + field.getType().getName() + ",类名: " + obj.getClass() + ", 属性名: " + field.getName());
        }
    }


}
